import asyncio
import traceback
from fastapi import FastAPI, UploadFile, File, HTTPException
from fastapi.middleware.cors import CORSMiddleware
import pandas as pd
import os
from datetime import datetime
import httpx
import json
import logging
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type

app = FastAPI()

# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

# 配置CORS
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

UPLOAD_DIR = "uploads"
os.makedirs(UPLOAD_DIR, exist_ok=True)

# 修改重试装饰器
@retry(
    stop=stop_after_attempt(10),
    wait=wait_exponential(multiplier=1, min=4, max=60),
    retry=retry_if_exception_type((httpx.HTTPStatusError, httpx.ReadTimeout, httpx.ConnectTimeout))
)
async def call_ai_service(client, url, payload, headers):
    try:
        response = await client.post(url, json=payload, headers=headers, timeout=500.0)
        response.raise_for_status()
        return response.json()
    except (httpx.HTTPStatusError, httpx.ReadTimeout) as e:
        logger.error(f"HTTP error or timeout occurred: {str(e)}")
        logger.error(f"Full traceback: {traceback.format_exc()}")
        raise
    except Exception as e:
        logger.error(f"An unexpected error occurred in call_ai_service: {str(e)}")
        logger.error(f"Full traceback: {traceback.format_exc()}")
        raise

async def process_batch(client, ai_service_url, headers, batch, i):
    try:
        prompt = f"""
        请根据以下时序数据生成一个结构化的中文描述，用于构建时序知识图谱。请遵循以下格式：

        1. 实体列表：
           - 列出所有实体（如设备、参数、指标等）。

        2. 属性定义：
           - 对于每个实体，列出其属性。
           格式：[实体].[属性名]

        3. 关系定义：
           - 描述实体之间的关系。
           格式：[源实体]-[关系类型]->[目标实体]:[关系描述]

        4. 时序变化：
           - 描述重要属性随时间的变化(指明具体时间)，包括趋势、异常值和周期性模式。

        5. 关键事件：
           - 标识并描述数据中的关键事件或显著变化。

        请确保描述简洁明了，便于后续处理和转换为知识图谱。

        数据：
        {json.dumps(batch, indent=2, ensure_ascii=False)}
        """

        payload = {
            "model": "Qwen2.5-72B-Instruct-int4",
            "messages": [
                {"role": "system", "content": "你是一个专门用于生成结构化时序知识图谱描述的AI助手。请使用中文回答。"},
                {"role": "user", "content": prompt}
            ],
            "stream": False
        }
        logger.info(f"正在发送请求到AI服务，处理第{i+1}批数据")
        ai_response = await call_ai_service(client, ai_service_url, payload, headers)
        logger.info(f"成功接收AI服务对第{i+1}批数据的响应")
        return ai_response['choices'][0]['message']['content'].strip()
    except Exception as e:
        logger.error(f"处理第{i+1}批数据时发生错误: {str(e)}")
        logger.error(f"完整的错误追踪: {traceback.format_exc()}")
        return None
async def merge_responses(responses, ai_service_url, headers):
    try:
        prompt = f"""
        请基于以下多个时间段的结构化描述，生成一个综合的、高度结构化的时序知识图谱描述。遵循以下格式：

        1. 实体定义
           - 列出所有唯一实体，保留原始信息

        2. 属性定义
           - 合并所有时间段的属性定义，消除重复
           格式：[实体].[属性名]

        3. 关系定义
           - 合并所有时间段的关系定义，消除重复
           格式：[源实体]-[关系类型]->[目标实体]:[关系描述]

        4. 时序变化
           - 综合描述各属性在整个时间范围内(指明具体时间)的变化趋势、周期性和异常

        5. 关键事件时间线
           - 按时间顺序列出所有关键事件

        请确保最终输出简洁、结构清晰，便于直接用于构建知识图谱。

        原始描述：
        {json.dumps(responses, ensure_ascii=False, indent=2)}
        """

        payload = {
            "model": "Qwen2.5-72B-Instruct-int4",
            "messages": [
                {"role": "system", "content": "你是一个专门用于生成高度结构化、详细的时序知识图谱描述的AI助手。请使用中文回答，并严格遵循给定的输出格式和要求。"},
                {"role": "user", "content": prompt}
            ],
            "stream": False
        }

        async with httpx.AsyncClient(timeout=300.0) as client:
            logger.info("正在发送合并请求到AI服务")
            response = await call_ai_service(client, ai_service_url, payload, headers)
            logger.info("成功接收AI服务的合并响应")
            return response['choices'][0]['message']['content'].strip()

    except Exception as e:
        logger.error(f"合并响应时发生错误: {str(e)}")
        logger.error(f"完整的错误追踪: {traceback.format_exc()}")
        raise
    
@app.post("/upload-csv")
async def upload_csv(file: UploadFile = File(...)):
    try:
        # 保存文件，修改文件名以去除中文
        sanitized_filename = file.filename.replace("数据", "data")  # 替换中文字符
        file_path = os.path.join(
            UPLOAD_DIR, f"{datetime.now().strftime('%Y%m%d%H%M%S')}_{sanitized_filename}")
        with open(file_path, "wb") as buffer:
            buffer.write(await file.read())

        return {"message": "File uploaded successfully", "filename": os.path.basename(file_path)}
    except Exception as e:
        return {"error": str(e)}


@app.get("/list-csv-files")
async def list_csv_files():
    files = [f for f in os.listdir(UPLOAD_DIR) if f.endswith('.csv')]
    return {"files": files}


@app.get("/get-csv-data/{filename}")
async def get_csv_data(filename: str):
    try:
        file_path = os.path.join(UPLOAD_DIR, filename)
        if not os.path.exists(file_path):
            return {"error": "File not found"}

        df = pd.read_csv(file_path)
        data = df.to_dict(orient='records')
        return {"data": data}
    except Exception as e:
        return {"error": str(e)}


@app.delete("/delete-csv-file/{filename}")
async def delete_csv_file(filename: str):
    try:
        file_path = os.path.join(UPLOAD_DIR, filename)
        if not os.path.exists(file_path):
            return {"error": "File not found"}

        os.remove(file_path)
        return {"message": f"File {filename} deleted successfully"}
    except Exception as e:
        return {"error": str(e)}


@app.post("/generate-knowledge-graph")
async def generate_knowledge_graph(file: UploadFile = File(...)):
    try:
        # 保存上传的CSV文件
        file_path = os.path.join(UPLOAD_DIR, f"{datetime.now().strftime('%Y%m%d%H%M%S')}_{file.filename}")
        with open(file_path, "wb") as buffer:
            buffer.write(await file.read())
        
        logger.info(f"成功保存CSV文件: {file_path}")
        
        # 读取CSV数据
        df = pd.read_csv(file_path)
        
        # 检查日期列名
        date_column = None
        possible_date_columns = ['Date', 'date', 'TIME', 'time', 'Timestamp', 'timestamp', 'DateTime', 'datetime','Datetime']
        
        for col in possible_date_columns:
            if col in df.columns:
                date_column = col
                break
        
        if date_column is None:
            raise HTTPException(status_code=400, detail="CSV文件中未找到日期列。请确保文件包含以下列名之一: " + ", ".join(possible_date_columns))
        
        data = df.to_dict(orient='records')
        logger.info(f"成功读取CSV数据,共{len(data)}条记录")

        # 按日期对数据进行分组
        date_groups = {}
        for record in data:
            date = record[date_column]
            if date not in date_groups:
                date_groups[date] = []
            date_groups[date].append(record)

        # 将分组后的数据转换为chunks,每3个日期一组
        dates = sorted(date_groups.keys())
        chunks = []
        current_chunk = []
        current_chunk_data = []
        for date in dates:
            current_chunk.append(date)
            current_chunk_data.extend(date_groups[date])
            
            if len(current_chunk) >= 3:
                chunks.append(current_chunk_data)
                current_chunk = []
                current_chunk_data = []

        if current_chunk_data:  # 添加最后一个不完整的chunk
            chunks.append(current_chunk_data)

        # 限制chunks数量为3
        max_chunks = 3
        if len(chunks) > max_chunks:
            chunks = chunks[:max_chunks]
            logger.info(f"限制处理前{max_chunks}个数据块,共{len(chunks)}个数据块")
        else:
            logger.info(f"处理所有{len(chunks)}个数据块")

        # 准备AI服务调用
        ai_service_url = "http://1.15.125.13:3033/v1/chat/completions"
        headers = {
            "Authorization": "Bearer sk-3033&5004",
            "Content-Type": "application/json"
        }

        # 并发处理所有chunks
        async def process_all_chunks():
            async with httpx.AsyncClient(timeout=300.0) as client:
                tasks = []
                for i, chunk in enumerate(chunks):
                    logger.info(f"创建第{i+1}/{len(chunks)}个chunk的任务")
                    logger.info(f"第{i+1}个chunk包含以下日期的数据: {[record[date_column] for record in chunk[:5]]}...")
                    tasks.append(process_batch(client, ai_service_url, headers, chunk, i))
                    await asyncio.sleep(1)  # 添加短暂延迟以避免请求过于密集
                return await asyncio.gather(*tasks)

        logger.info("开始并行处理chunks")
        all_responses = await process_all_chunks()
        logger.info(f"从AI服务接收到{len(all_responses)}个响应")

        # 过滤掉None响应并合并结果
        valid_responses = [r for r in all_responses if r is not None]
        if not valid_responses:
            raise HTTPException(status_code=500, detail="未能生成任何有效的响应")

        # 使用大模型合并所有响应
        merged_response = await merge_responses(valid_responses, ai_service_url, headers)
        
        # 保存结果
        output_filename = f"knowledge_graph_{datetime.now().strftime('%Y%m%d%H%M%S')}.txt"
        output_path = os.path.join(UPLOAD_DIR, output_filename)
        with open(output_path, "w", encoding="utf-8") as f:
            f.write(merged_response)
        
        # 清理临时CSV文件
        os.remove(file_path)
        
        logger.info(f"知识图谱文本已保存至 {output_path}")
        return {
            "message": "知识图谱文本生成成功", 
            "filename": output_filename,
            "chunks_processed": len(chunks),
            "valid_responses": len(valid_responses)
        }
    except Exception as e:
        logger.error(f"发生意外错误: {str(e)}")
        logger.error(f"完整的错误追踪: {traceback.format_exc()}")
        # 确保清理临时文件
        if 'file_path' in locals():
            try:
                os.remove(file_path)
            except:
                pass
        raise HTTPException(status_code=500, detail=f"发生意外错误: {str(e)}")

@app.get("/test-ai-service")
async def test_ai_service():
    """
    测试AI服务是否正常运行的简单接口
    返回AI服务对"你好"的响应
    """
    try:
        ai_service_url = "http://1.15.125.13:3033/v1/chat/completions"
        headers = {
            "Authorization": "Bearer sk-3033&5004",
            "Content-Type": "application/json"
        }
        message = """请根据以下时序数据生成一个详细的文本描述，该描述应便于后续构建时序知识图谱。请遵循以下指南：
            1. 时间标记：明确指出每个数据点的时间戳。
            2. 实体识别：识别并列出数据中的主要实体（如设备、参数、指标等）。
            3. 关系描述：描述实体之间的关系，特别是随时间变化的关系。
            4. 趋势分析：分析并描述各指标随时间的变化趋势。
            5. 异常检测：指出任何异常或显著变化的数据点。
            6. 周期性模式：如果存在，描述任何周期性或重复性模式。
            7. 关键事件：标识并描述可能的关键事件或转折点。
            8. 实体属性：列出每个实体的关键属性及其变化。
            9. 数范围：提供重要指标的数值范围。
            10. 相关性：指出不同指标之间可能存在的相关性。
            请以结构化的方式呈现信息，使用明确的标题和小节。确保描述简洁明了，便于后续处理和转换为知识图谱。
            数据：
                "Date": "2022-01-01",
                "烟片加料__出口物料含水率实际值": 17.9493,
                "烟片加料__出口物料温度": 39.2766,
                " 贮叶房温度": 34.28,
                " 贮叶房相对湿度": 69.57,
                "叶丝加料__热风温度实际值": 79.9715,
                "叶丝加料__带料压空压力": 0.5948,
                "叶丝加料__加料比例实际值": 2.7399,
                "叶丝加料__补偿蒸汽开度实际值": -5.0089,
                "叶丝加料__入口物料含水率": 17.6417,
                "叶丝加料__出口物料含水率实际值": 19.0143,
                "叶丝加料__出口物料温度实际值": 37.9171,
                "叶丝加料__加料累计精度": 0.0046,
                "叶丝加料__排潮风门开度实际值": 34.5597,
                " 制丝车间温度": 25.63,
                " 制丝车间相对湿度": 59.07,
                "叶丝增温增湿(SIROX)_工作蒸汽压力": 2.2163,
                "叶丝增温增湿(SIROX)_工作蒸汽流量实际值": 546.5746,
                "叶丝增温增湿(SIROX)__入口物料含水率": 18.6403,
                "叶丝增温增湿(SIROX)__出口物料温度": 66.7742,
                "叶丝干燥(薄板)__筒壁温度(一区)实际值": 129.9715,
                "叶丝干燥(薄板)__筒壁温度(二区)实际值": 119.9682,
                "叶丝干燥(薄板)__热风温度实际值": 115.0287,
                "叶丝干燥(薄板)__热风风速实际值": 0.5314,
                "叶丝干燥(薄板)__排潮负压实际值": -19.6586,
                "叶丝干燥(薄板)__出口物料含水率实际值": 12.8293,
                "叶丝干燥(薄板)__出口物料温度": 56.2191,
                "叶丝干燥(薄板)__烘丝机蒸汽压力值": 8.9873,
                " 掺配间温度": 26.58,
                " 掺配间相对湿度": 63.52,
                "比例掺配__薄片丝掺配比例实际值": 7.9995,
                "比例掺配__薄片丝掺配精度": 0.0137,
                " 加香间温度": 27.4,
                " 加香间相对湿度": 64.75,
                "加香__加香比例实际值": 0.6,
                "加香__加香累计精度": 0.0114,
                "加香__出口物料含水率": 12.8085
            ,
            
                "Date": "2022-01-02",
                "烟片加料__出口物料含水率实际值": 17.8611,
                "烟片加料__出口物料温度": 40.1136,
                " 贮叶房温度": 34.1,
                " 贮叶房相对湿度": 69.4,
                "叶丝加料__热风温度实际值": 79.9962,
                "叶丝加料__带料压空压力": 0.6079,
                "叶丝加料__加料比例实际值": 2.7399,
                "叶丝加料__补偿蒸汽开度实际值": -5.0052,
                "叶丝加料__入口物料含水率": 17.3929,
                "叶丝加料__出口物料含水率实际值": 18.7962,
                "叶丝加料__出口物料温度实际值": 38.0645,
                "叶丝加料__加料累计精度": 0.0044,
                "叶丝加料__排潮风门开度实际值": 34.5452,
                " 制丝车间温度": 25.53,
                " 制丝车间相对湿度": 59.21,
                "叶丝增温增湿(SIROX)_工作蒸汽压力": 2.2425,
                "叶丝增温增湿(SIROX)_工作蒸汽流量实际值": 550.4909,
                "叶丝增温增湿(SIROX)__入口物料含水率": 18.2849,
                "叶丝增温增湿(SIROX)__出口物料温度": 66.9493,
                "叶丝干燥(薄板)__筒壁温度(一区)实际值": 129.9759,
                "叶丝干燥(薄板)__筒壁温度(二区)实际值": 119.9663,
                "叶丝干燥(薄板)__热风温度实际值": 114.9955,
                "叶丝干燥(薄板)__热风风速实际值": 0.4762,
                "叶丝干燥(薄板)__排潮负压实际值": -19.2405,
                "叶丝干燥(薄板)__出口物料含水率实际值": 12.8012,
                "叶丝干燥(薄板)__出口物料温度": 55.9813,
                "叶丝干燥(薄板)__烘丝机蒸汽压力值": 9.0134,
                " 掺配间温度": 26.75,
                " 掺配间相对湿度": 64.18,
                "比例掺配__薄片丝掺配比例实际值": 7.9998,
                "比例掺配__薄片丝掺配精度": 0.0124,
                " 加香间温度": 27.39,
                " 加香间相对湿度": 64.61,
                "加香__加香比例实际值": 0.6,
                "加香__加香累计精度": 0.0136,
                "加香__出口物料含水率": 12.748
        """
        payload = {
            "model": "Qwen2.5-72B-Instruct-int4",
            "messages": [
                {"role": "user", "content": message}
            ],
            "stream": False
        }
        logger.info("Testing AI service with simple greeting")
        
        async with httpx.AsyncClient() as client:
            try:
                response = await client.post(
                    ai_service_url, 
                    json=payload, 
                    headers=headers, 
                    timeout=300.0
                )
                response.raise_for_status()
                
                result = response.json()
                ai_response = result['choices'][0]['message']['content'].strip()
                
                logger.info("Successfully received response from AI service")
                return {
                    "status": "success",
                    "message": "AI service is working",
                    "response": ai_response
                }
                
            except httpx.HTTPStatusError as e:
                logger.error(f"HTTP error occurred: {e.response.text}")
                return {
                    "status": "error",
                    "message": f"HTTP error: {e.response.status_code}",
                    "details": e.response.text
                }
            except httpx.RequestError as e:
                logger.error(f"Request error occurred: {str(e)}")
                logger.error(f"Request error details: {e}")
                return {
                    "status": "error",
                    "message": "Failed to connect to AI service",
                    "details": str(e)
                }
            
    except Exception as e:
        logger.error(f"An unexpected error occurred: {str(e)}", exc_info=True)
        return {
            "status": "error",
            "message": "An unexpected error occurred",
            "details": str(e)
        }